package com.hmkj.core.service.order.impl;

import com.alibaba.fastjson.JSON;
import com.hmkj.common.baseMapper.GenericMapper;
import com.hmkj.common.utils.BigDecimalUtils;
import com.hmkj.common.utils.DateUtils;
import com.hmkj.common.utils.OrderNoUtils;
import com.hmkj.core.constant.CacheID;
import com.hmkj.core.constant.PointNid;
import com.hmkj.core.constant.RiskNID;
import com.hmkj.core.exception.BussinessException;
import com.hmkj.core.mapper.amount.AccountLogSimulateMapper;
import com.hmkj.core.mapper.amount.AccountSimulateMapper;
import com.hmkj.core.mapper.investor.InvestorSimulateMapper;
import com.hmkj.core.mapper.operate.AgentMapper;
import com.hmkj.core.mapper.order.OrderMapper;
import com.hmkj.core.mapper.order.OrderRelationMapper;
import com.hmkj.core.mapper.order.OrderSimulateMapper;
import com.hmkj.core.mapper.user.UserMapper;
import com.hmkj.core.model.order.OrderSimulateModel;
import com.hmkj.core.model.quote.RealTimeData;
import com.hmkj.core.po.amount.AccountLogSimulate;
import com.hmkj.core.po.amount.AccountSimulate;
import com.hmkj.core.po.commo.Commo;
import com.hmkj.core.po.investor.InvestorSimulate;
import com.hmkj.core.po.operate.Agent;
import com.hmkj.core.po.order.Order;
import com.hmkj.core.po.order.OrderRelation;
import com.hmkj.core.po.order.OrderSimulate;
import com.hmkj.core.po.user.User;
import com.hmkj.core.service.cache.RedisService;
import com.hmkj.core.service.operate.PointRuleService;
import com.hmkj.core.service.order.OrderSimulateService;
import com.hmkj.core.service.risk.RiskParamService;
import com.hmkj.core.service.user.UserService;
import com.xiaoleilu.hutool.util.RandomUtil;
import com.xiaoleilu.hutool.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;

/**
 * 模拟策略表业务相关Service接口实现<br>
 *
 * @author eproo
 * @ClassName: OrderSimulateServiceImpl
 * @date 2017-11-10 11:33:05
 */
@Slf4j
@Service
@Scope("prototype")
@Transactional(rollbackFor = Exception.class)
public class OrderSimulateServiceImpl implements OrderSimulateService {

    @Resource
    private RedisService redisService;
    @Resource
    private RiskParamService riskParamService;
    @Resource
    private UserService userService;
    @Resource
    private OrderSimulateMapper orderSimulateMapper;
    @Resource
    private UserMapper userMapper;
    @Resource
    private AccountSimulateMapper accountSimulateMapper;
    @Resource
    private AccountLogSimulateMapper accountLogSimulateMapper;
    @Resource
    private AgentMapper agentMapper;
    @Resource
    private OrderRelationMapper orderRelationMapper;
    @Resource
    private PointRuleService pointRuleService;
    @Resource
    private InvestorSimulateMapper investorSimulateMapper;
    @Resource
    private OrderMapper orderMapper;


    @Override
    public GenericMapper<OrderSimulate, Integer> _getMapper() {
        return orderSimulateMapper;
    }

    @Override
    public void takingOrder(OrderSimulate orderSimulate) {
        InvestorSimulate is = radomInvestorFree(orderSimulate.getAgentId());
        //后台已知投资人接单
        handTradeEntrust(orderSimulate, is);
    }

    @Override
    public void flowOrder(OrderSimulate orderSimulate) {
        //更改订单状态（已流单）
        orderSimulate.setStatus(OrderSimulate.STATUS.T8.code);
        int result4 = orderSimulateMapper.updateByPrimaryKeySelective(orderSimulate);
        //点买人的模拟账户
        AccountSimulate userAccountSimulate = accountSimulateMapper.selectByUID(orderSimulate.getUserId());
        // 手续费
        BigDecimal totalFee = orderSimulate.getFee().multiply(new BigDecimal(orderSimulate.getNumber())).setScale(2, BigDecimal.ROUND_HALF_UP);
        //退还点买人履约保证金，退还点买人手续费，
        int result1 = accountSimulateMapper.updateAccountSimulate(BigDecimal.ZERO, BigDecimalUtils.add(orderSimulate.getDeposit(), totalFee), BigDecimalUtils.add(orderSimulate.getDeposit(), totalFee).negate(), orderSimulate.getUserId());
//        if (null != orderSimulate.getInvestId()) {
//            //投资人的模拟账户
//            AccountSimulate investorAccountSimulate = accountSimulateMapper.selectByUID(orderSimulate.getInvestId());
//            //退还投资人止盈保证金，
//            int result3 = accountSimulateMapper.updateAccountSimulate(BigDecimal.ZERO, orderSimulate.getProfitDeposit(), orderSimulate.getProfitDeposit().negate(), orderSimulate.getInvestId());
//            if (result3 != 1) throw new BussinessException("流单失败");
//            //增加投资人资金记录
//            int n1 = accountLogSimulateMapper.insert(AccountLogSimulate.buildLog(userAccountSimulate.getUserId(), orderSimulate.getFee(), AccountLogSimulate.TYPE.T16, investorAccountSimulate, "解冻止盈保证金" + orderSimulate.getProfitDeposit() + "元。模拟订单ID：" + orderSimulate.getId()));
//            if (n1 != 1) throw new BussinessException("添加解冻止盈保证金资金记录失败");
//        }
        if (result1 != 1 || result4 != 1) {
            throw new BussinessException("流单失败");
        }
        //增加点买人资金记录
        int m1 = accountLogSimulateMapper.insert(AccountLogSimulate.buildLog(orderSimulate.getUserId(), orderSimulate.getDeposit(), AccountLogSimulate.TYPE.T19, userAccountSimulate, "解冻履约保证金" + orderSimulate.getDeposit() + "元。模拟订单ID：" + orderSimulate.getId()));
        if (m1 != 1) throw new BussinessException("添加解冻履约保证金资金记录失败");
        int m2 = accountLogSimulateMapper.insert(AccountLogSimulate.buildLog(userAccountSimulate.getUserId(), totalFee, AccountLogSimulate.TYPE.T16, userAccountSimulate, "解冻手续费" + totalFee + "元。模拟订单ID：" + orderSimulate.getId()));
        if (m2 != 1) throw new BussinessException("添加点买人退还手续费资金记录失败");
    }

    @Override
    public void cancelOrder(OrderSimulate orderSimulate) {
        //获取当前订单用户角色
        User user = userService.selectByPrimaryKey(orderSimulate.getUserId());

        //更改订单状态（已撤单）
        orderSimulate.setStatus(OrderSimulate.STATUS.T10.code);
        int result4 = orderSimulateMapper.updateByPrimaryKeySelective(orderSimulate);
        //点买人的模拟账户
        AccountSimulate userAccountSimulate = accountSimulateMapper.selectByUID(orderSimulate.getUserId());
        // 手续费
        BigDecimal totalFee = orderSimulate.getFee().multiply(new BigDecimal(orderSimulate.getNumber())).setScale(2, BigDecimal.ROUND_HALF_UP);
        //退还点买人履约保证金，退还点买人手续费，
        int result1 = accountSimulateMapper.updateAccountSimulate(BigDecimal.ZERO, BigDecimalUtils.add(orderSimulate.getDeposit(), totalFee), BigDecimalUtils.add(orderSimulate.getDeposit(), totalFee).negate(), orderSimulate.getUserId());

//        if (null != orderSimulate.getInvestId()) {
//            //投资人的模拟账户
//            AccountSimulate investorAccountSimulate = accountSimulateMapper.selectByUID(orderSimulate.getInvestId());
//            //退还投资人止盈保证金，
//            int result3 = accountSimulateMapper.updateAccountSimulate(BigDecimal.ZERO, orderSimulate.getProfitDeposit(), orderSimulate.getProfitDeposit().negate(), orderSimulate.getInvestId());
//            if (result3 != 1) throw new BussinessException("撤单失败");
//            //增加投资人资金记录
//            int n1 = accountLogSimulateMapper.insert(AccountLogSimulate.buildLog(userAccountSimulate.getUserId(), orderSimulate.getFee(), AccountLogSimulate.TYPE.T16, investorAccountSimulate, "解冻止盈保证金" + orderSimulate.getProfitDeposit() + "元。模拟订单ID：" + orderSimulate.getId()));
//            if (n1 != 1) throw new BussinessException("添加解冻止盈保证金资金记录失败");
//        }
        if (result1 != 1 || result4 != 1) {
            throw new BussinessException("撤单失败");
        }
        //增加点买人资金记录
        int m1 = accountLogSimulateMapper.insert(AccountLogSimulate.buildLog(orderSimulate.getUserId(), orderSimulate.getDeposit(), AccountLogSimulate.TYPE.T19, userAccountSimulate, "解冻履约保证金" + orderSimulate.getDeposit() + "元。模拟订单ID：" + orderSimulate.getId()));
        if (m1 != 1) throw new BussinessException("添加解冻履约保证金资金记录失败");
        int m2 = accountLogSimulateMapper.insert(AccountLogSimulate.buildLog(userAccountSimulate.getUserId(), totalFee, AccountLogSimulate.TYPE.T16, userAccountSimulate, "解冻手续费" + totalFee + "元。模拟订单ID：" + orderSimulate.getId()));
        if (m2 != 1) throw new BussinessException("添加点买人退还手续费资金记录失败");

        // 操盘手处理跟单撤单
        if (user.getType() == User.TYPE.T3.code) {
            //获取模拟盘操盘手下单对应实盘跟单订单
            List<OrderRelation> orderRelations = orderRelationMapper.select(new OrderRelation(orderRelation -> {
                orderRelation.setSimId(orderSimulate.getId());
                orderRelation.setDelFlag(OrderRelation.DELFLAG.NORMAL.code);
            }));
            orderRelations.forEach(or -> {
                List<Order> orderList = orderMapper.selectLikeOrderNoT2(or.getRealOrderNo() + "%");
                //对应实盘订单全部撤单
                orderList.forEach(order -> {
                    try {
                        Map<String, Object> data = new HashMap<>();
                        data.put("orderId", order.getId());
                        //队列处理跟单撤单业务
                        redisService.push(CacheID.QUEUE_FOLLOW_CANCEL, data);
                    } catch (Exception e) {
                        throw new BussinessException("操盘手撤单，对应跟单实盘撤单失败，订单Id:" + order.getId());
                    }
                });
            });
        }


        // 限价单测单需要删除缓存中的记录
        if (orderSimulate.getOpenWay().intValue() == OrderSimulate.OPENWAY.T2.code) {
            String key = CacheID.LINIT_ORDER_SL + orderSimulate.getCommoId() + ":" + orderSimulate.getDirection() + ":" + orderSimulate.getEntrustPrice().setScale(4).toString();
            redisService.mapDel(key, orderSimulate.getId().toString());
        }
    }

    @Override
    public List<OrderSimulateModel> userSum() {
        return null;
    }


    @Override
    public void doTrade(Integer userId, Integer commoId, Integer num, Integer lostPoint, Integer point, Integer openWay, Integer direction, BigDecimal entrustPrice, String resource) {
        if (openWay == OrderSimulate.OPENWAY.T2.code) {
            if (null == entrustPrice || entrustPrice.compareTo(BigDecimal.ZERO) != 1 || entrustPrice.toString().length() > 20) {
                throw new BussinessException("委托价格有误");
            }
        }
        final Commo commo = redisService.get(CacheID.COMMO_ID_PREFIX + commoId, Commo.class);
        if (commo == null) throw new BussinessException("交易品种不存在");
        if (commo.getStatus().intValue() == Commo.STATUS.T2.code) throw new BussinessException("交易品种已禁用");
//        Agent agent = agentMapper.selectOne(new Agent(t -> {
//            t.setDomain(resource);
//            t.setStatus(Agent.STATUS.T1.code);
//            t.setDelFlag(Agent.DELFLAG.NORMAL.code);
//        }));
        User user = userMapper.selectByPrimaryKey(userId);
        Agent agent = agentMapper.selectByPrimaryKey(user.getAgentId());
        if (agent == null) throw new BussinessException("查询代理商是失败");
        // 交易时间校验
        if (!riskParamService.isTradeTime(commoId)) throw new BussinessException("当前非交易时间，下单失败");
        // 暂停交易校验
        if(riskParamService.isTradePause(commoId)) throw new BussinessException("当前品种暂停交易，下单失败");
        // 查询用户资金账户
        final AccountSimulate accountSimulate = accountSimulateMapper.selectByUID(userId);
        // 下单参数校验
        String checkMsg = checkTrade(accountSimulate, commoId, num, lostPoint, point, openWay, entrustPrice, direction, resource);
        if (StrUtil.isNotBlank(checkMsg)) throw new BussinessException(checkMsg);
        // 每点合约价格
        BigDecimal pointVlaue = riskParamService.getParamValue(RiskNID.POINT_VALUE, commoId, BigDecimal.class);
        // 止盈倍数
        BigDecimal profitStopTimes = riskParamService.getParamValue(RiskNID.PROFIT_STOP_TIMES, commoId, BigDecimal.class);
        // 对人民币汇率
        BigDecimal exchangeRate = riskParamService.getParamValue(RiskNID.EXCHANGE_RATE, commoId, BigDecimal.class);
        // 止损比例
        BigDecimal loseStopRate = riskParamService.getParamValue(RiskNID.LOSE_STOP_RATE, commoId, BigDecimal.class);

        // 每手手续费
        BigDecimal fee = riskParamService.getFeeByAgent(commoId, agent.getId());
        // 点买人盈利分成
        BigDecimal allotRate = riskParamService.getParamValue(RiskNID.ALLOT_RATE, commoId, BigDecimal.class);
        // 止损金额 = 止损点数 * 每点合约价值 * 数量 * 汇率
        BigDecimal loseStop = pointVlaue.multiply(new BigDecimal(lostPoint * num)).multiply(exchangeRate);
        // 履约保证金 = 止损金额 / 止损比例
        BigDecimal deposit = loseStop.divide(loseStopRate, 2, BigDecimal.ROUND_HALF_UP);
        // 止盈保证金 = 止损金额 * 止盈倍数
        BigDecimal profitDeposit = loseStop.multiply(profitStopTimes).setScale(2, BigDecimal.ROUND_HALF_UP);
        RealTimeData realTimeData = redisService.get(CacheID.QUOTE_REAL_PREFIX + commoId, RealTimeData.class);
        // 计算实际委托价格
        BigDecimal entrustPriceReal = getEntrustPrice(openWay, direction, entrustPrice, realTimeData);

        // 止损下滑价格  = 止损点数 * 最小波动点
        BigDecimal lsp = new BigDecimal(lostPoint).multiply(riskParamService.getParamValue(RiskNID.WAVE_POINT_MIN, commoId, BigDecimal.class)).setScale(4, BigDecimal.ROUND_HALF_UP);
        BigDecimal loseStopMinPrice;
        BigDecimal profitStopMaxPrice;
        if (direction == OrderSimulate.DIRECTION.T1.code) {
            // 最低止损价格 = 委托价 - 止损下滑价格
            loseStopMinPrice = entrustPriceReal.subtract(lsp).compareTo(BigDecimal.ZERO) != 1 ? BigDecimal.ZERO : entrustPriceReal.subtract(lsp);
            // 最高止盈价格 = 委托价 + （止损下滑价格 * 止盈倍数）
            profitStopMaxPrice = entrustPriceReal.add(lsp.multiply(profitStopTimes)).compareTo(BigDecimal.ZERO) != 1 ? BigDecimal.ZERO : entrustPriceReal.add(lsp.multiply(profitStopTimes));
        } else {
            // 最低止损价格 = 委托价 + 止损下滑价格
            loseStopMinPrice = entrustPriceReal.add(lsp).compareTo(BigDecimal.ZERO) != 1 ? BigDecimal.ZERO : entrustPriceReal.add(lsp);
            // 最高止盈价格 = 委托价 - （止损下滑价格 * 止盈倍数）
            profitStopMaxPrice = entrustPriceReal.subtract(lsp.multiply(profitStopTimes)).compareTo(BigDecimal.ZERO) != 1 ? BigDecimal.ZERO : entrustPriceReal.subtract(lsp.multiply(profitStopTimes));
        }
        // 手续费
        BigDecimal totalFee = fee.multiply(new BigDecimal(num)).setScale(2, BigDecimal.ROUND_HALF_UP);
        // 点买人下单所需总金额
        BigDecimal totalAmount = totalFee.add(deposit).setScale(2, BigDecimal.ROUND_HALF_UP);
        // 账户余额校验
        if (accountSimulate.getAvailable().compareTo(totalAmount) == -1) throw new BussinessException("模拟账户余额不足，下单失败");
        // 保存策略信息
        BigDecimal finalLoseStopMinPrice = loseStopMinPrice;
        BigDecimal finalProfitStopMaxPrice = profitStopMaxPrice;
        BigDecimal finalEntrustPrice = entrustPriceReal;
        String orderNo = OrderNoUtils.getSerialNumber();
        OrderSimulate orderSimulate = new OrderSimulate(t -> {
            t.setCommoId(commoId);
            t.setCode(commo.getCode());
            t.setName(commo.getName());
            t.setContract(riskParamService.getContractNo(commoId));
            t.setOrderNo(orderNo);
            t.setUserId(userId);
            t.setDirection(direction);
            t.setEntrustPrice(finalEntrustPrice);
            t.setOpenWay(openWay);
            t.setNumber(num);
            t.setLoseStopPoint(lostPoint);
            t.setLoseStopPrice(finalLoseStopMinPrice);
            t.setLoseStopMinPrice(finalLoseStopMinPrice);
            t.setProfitStopPrice(finalProfitStopMaxPrice);
            t.setProfitStopMaxPrice(finalProfitStopMaxPrice);
            t.setDeposit(deposit);
            t.setProfitDeposit(profitDeposit);
            t.setFee(fee);
            t.setAllotRate(allotRate);
            t.setResource(agent.getDomain());
            t.setAgentId(agent.getId());
            t.setStatus(OrderSimulate.STATUS.T1.code);
            t.setAutoSettleTime(riskParamService.getAutoSettleTime(commoId));
        });

        orderSimulateMapper.insert(orderSimulate);

        orderSimulate = orderSimulateMapper.selectOne(new OrderSimulate(t -> t.setOrderNo(orderNo)));

        // 修改点买人账户金额（履约保证金+手续费），并记录资金流水
        int c2 = accountLogSimulateMapper.insert(AccountLogSimulate.buildLog(userId, totalFee, AccountLogSimulate.TYPE.T15, accountSimulate, "下单成功，冻结交易综合费：" + totalFee + "元。订单ID:" + orderSimulate.getId()));
        if (c2 != 1) throw new BussinessException("保存资金流水失败");
        int c3 = accountLogSimulateMapper.insert(AccountLogSimulate.buildLog(userId, deposit, AccountLogSimulate.TYPE.T18, accountSimulate, "下单成功，冻结履约保证金：" + deposit + "元。订单ID:" + orderSimulate.getId()));
        if (c3 != 1) throw new BussinessException("保存资金流水失败");
        int c1 = accountSimulateMapper.updateAccountSimulate(BigDecimal.ZERO, totalAmount.negate(), totalAmount, userId);
        if (c1 != 1) throw new BussinessException("更新点买人账户资金失败");

        // 匹配投资人
        orderSimulate = doMatchInvestor(orderSimulate);

        // 市价单直接匹配投资人进行下单
        if (OrderSimulate.OPENWAY.T1.code == openWay) {//限价单若等于行情价 则也可及时成交
            // 匹配到投资人，并下单
            doTradeEntrust(orderSimulate);
        } else {
            if (direction == OrderSimulate.DIRECTION.T1.code && entrustPrice.compareTo(realTimeData.getNp()) >= 0) {
                // 限价买  并且委托价 >= 最新价  立即下单
                doTradeEntrust(orderSimulate);
            } else if (direction == OrderSimulate.DIRECTION.T2.code && entrustPrice.compareTo(realTimeData.getNp()) <= 0) {
                // 限价卖  并且委托价 <= 最新价  立即下单
                doTradeEntrust(orderSimulate);
            } else {
                // 缓存限价单，限价单需要实时匹配行情价进行下单
                String key = CacheID.LINIT_ORDER_SL + orderSimulate.getCommoId() + ":" + orderSimulate.getDirection() + ":" + entrustPrice.setScale(4).toString();
                redisService.mapPut(key, orderSimulate.getId().toString(), orderSimulate.getId().toString());
            }
        }

        // 操盘手处理跟单
        if (user.getType() == User.TYPE.T3.code) {
            //队列处理跟单下单业务
            redisService.push(CacheID.QUEUE_FOLLOW, orderSimulate);
        }

        try {
            // 发放积分
            pointRuleService.sendPoint(userId, PointNid.T5);
        } catch (Exception e) {
            log.error("发放积分失败，错误消息：{}", e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 匹配投资人
     *
     * @param orderSimulate
     */
    private OrderSimulate doMatchInvestor(OrderSimulate orderSimulate) {
        try {
            InvestorSimulate is = radomInvestorFree(orderSimulate.getAgentId());
            if (is != null) {
                // 匹配投资人成功，处理投资人账户资金
                // 修改投资人账户金额，并记录资金流水
//                int ic1 = accountSimulateMapper.updateAccountSimulate(BigDecimal.ZERO, orderSimulate.getProfitDeposit().negate(), orderSimulate.getProfitDeposit(), ia.getUserId());
//                if (ic1 != 1) throw new BussinessException("更新投资人账户资金失败");
//                int ic2 = accountLogSimulateMapper.insert(AccountLogSimulate.buildLog(ia.getUserId(), orderSimulate.getProfitDeposit(), AccountLogSimulate.TYPE.T21, ia, "匹配投资人成功，冻结止盈保证金：" + orderSimulate.getProfitDeposit() + "元，订单ID：" + orderSimulate.getId()));
//                if (ic2 != 1) throw new BussinessException("保存资金流水失败");

                // 更新委托编号，投资人
                orderSimulateMapper.updateByPrimaryKeySelective(new OrderSimulate(t -> {
                    t.setId(orderSimulate.getId());
                    t.setInvestId(is.getUserId());
                    t.setStatus(OrderSimulate.STATUS.T2.code);
                }));
            } else {
                throw new BussinessException("匹配投资人失败");
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new BussinessException(e.getMessage());
        }
        return orderSimulateMapper.selectByPrimaryKey(orderSimulate.getId());
    }

    /**
     * 下单
     *
     * @param orderSimulate
     */
    private synchronized void doTradeEntrust(OrderSimulate orderSimulate) {
        if (orderSimulate.getStatus() != OrderSimulate.STATUS.T2.code) throw new BussinessException("非委托中订单不能下单");
        if (orderSimulate.getInvestId() != null) {
            // 扣除点买人冻结的手续费
            AccountSimulate accountSimulate = accountSimulateMapper.selectByUID(orderSimulate.getUserId());
            BigDecimal fee = orderSimulate.getFee().multiply(new BigDecimal(orderSimulate.getNumber())).setScale(2, BigDecimal.ROUND_HALF_UP);
            int c2 = accountLogSimulateMapper.insert(AccountLogSimulate.buildLog(accountSimulate.getUserId(), fee, AccountLogSimulate.TYPE.T17, accountSimulate, "扣除交易综合费" + fee + "元。订单ID：" + orderSimulate.getId()));
            if (c2 != 1) throw new BussinessException("期货成交回报扣除交易综合费失败资金记录保存失败");
            int c1 = accountSimulateMapper.updateAccountSimulate(fee.negate(), BigDecimal.ZERO, fee.negate(), accountSimulate.getUserId());
            if (c1 != 1) throw new BussinessException("期货成交回报扣除交易综合费失败");
            // 更新委托编号，投资人
            orderSimulateMapper.updateByPrimaryKeySelective(new OrderSimulate(t -> {
                t.setId(orderSimulate.getId());
                t.setStatus(OrderSimulate.STATUS.T3.code);
                t.setOpenTime(new Date());
                t.setOpenPrice(orderSimulate.getEntrustPrice());
            }));
        }
    }

    /**
     * 后台已知投资人，接单
     *
     * @param orderSimulate
     */
    private void handTradeEntrust(OrderSimulate orderSimulate, InvestorSimulate is) {
        //校验投资人账户余额
//        if (BigDecimalUtils.isLessThan(ia.getAvailable(), orderSimulate.getProfitDeposit()))
//            throw new BussinessException("投资人账户余额不足");
        try {
            // 修改投资人账户金额，并记录资金流水
//            int ic1 = accountSimulateMapper.updateAccountSimulate(BigDecimal.ZERO, orderSimulate.getProfitDeposit().negate(), orderSimulate.getProfitDeposit(), ia.getUserId());
//            if (ic1 != 1) throw new BussinessException("更新投资人账户资金失败");
//            int ic2 = accountLogSimulateMapper.insert(AccountLogSimulate.buildLog(ia.getUserId(), orderSimulate.getProfitDeposit(), AccountLogSimulate.TYPE.T21, ia, "投资人接单成功，冻结止盈保证金：" + orderSimulate.getProfitDeposit() + "元，订单ID：" + orderSimulate.getId()));
//            if (ic2 != 1) throw new BussinessException("保存资金流水失败");

            // 扣除点买人冻结的手续费
            AccountSimulate accountSimulate = accountSimulateMapper.selectByUID(orderSimulate.getUserId());
            BigDecimal fee = orderSimulate.getFee().multiply(new BigDecimal(orderSimulate.getNumber())).setScale(2, BigDecimal.ROUND_HALF_UP);
            int c2 = accountLogSimulateMapper.insert(AccountLogSimulate.buildLog(accountSimulate.getUserId(), fee, AccountLogSimulate.TYPE.T17, accountSimulate, "扣除交易综合费" + fee + "元。订单ID：" + orderSimulate.getId()));
            if (c2 != 1) throw new BussinessException("期货成交回报扣除交易综合费失败资金记录保存失败");
            int c1 = accountSimulateMapper.updateAccountSimulate(fee.negate(), BigDecimal.ZERO, fee.negate(), accountSimulate.getUserId());
            if (c1 != 1) throw new BussinessException("期货成交回报扣除交易综合费失败");

            RealTimeData ss = redisService.get(CacheID.QUOTE_REAL_PREFIX + orderSimulate.getCommoId(), RealTimeData.class);
            if (null == ss || null == ss.getNp()) throw new BussinessException("获取行情最新价失败");

            // 更新委托编号，投资人
            orderSimulateMapper.updateByIdAndVersionSelective(new OrderSimulate(t -> {
                t.setId(orderSimulate.getId());
                t.setVersion(orderSimulate.getVersion());
                t.setInvestId(is.getUserId());
                t.setStatus(OrderSimulate.STATUS.T3.code);
                t.setOpenTime(new Date());
                t.setOpenPrice(ss.getNp());
            }));
        } catch (Exception e) {
            e.printStackTrace();
            throw new BussinessException(e.getMessage());
        }
    }

    /**
     * 随机分配符合条件的投资人（账户可用金额大于等于止盈保证金）
     *
     * @param profitDeposit 止盈保证金
     * @return
     */
    private AccountSimulate radomInvestor(BigDecimal profitDeposit) {
        List<AccountSimulate> list = accountSimulateMapper.selectInvestAccountSimulate(profitDeposit);
        if (CollectionUtils.isNotEmpty(list)) {
            return list.get(RandomUtil.randomInt(list.size()));
        }
        return null;
    }

    /**
     * 随机分配符合条件的投资人（不考虑资金状况）
     *
     * @
     */
    private InvestorSimulate radomInvestorFree(Integer agentId) {
        List<InvestorSimulate> list = investorSimulateMapper.selectByAgent(agentId);
        if (CollectionUtils.isNotEmpty(list)) {
            return list.get(RandomUtil.randomInt(list.size()));
        }
        return null;
    }

    /**
     * 计算委托价格
     *
     * @param openWay      开仓方式 1市价  2限价
     * @param direction    开仓方向 1  2限价
     * @param entrustPrice 限价单委托价格
     * @param realTimeData 最新行情数据
     * @return
     */
    private BigDecimal getEntrustPrice(Integer openWay, Integer direction, BigDecimal entrustPrice, RealTimeData realTimeData) {
        if (openWay == OrderSimulate.OPENWAY.T1.code) {
            // 市价单
            if (OrderSimulate.DIRECTION.T1.code == direction) {
                // 买委托价 = 卖一价
                return realTimeData.getAp1();
            } else {
                // 卖委托价 = 买一价
                return realTimeData.getBp1();
            }
        }
        return entrustPrice;
    }


    @Override
    public void doLimitOrderTrade(Integer commoId, BigDecimal np) {
        log.info("模拟--行情驱动限价单下单，品种ID：{},最新价：{}", commoId, np);
        // 买涨限价策略 委托价 大于等于 最新价  挂单
        String upperKeys = CacheID.LINIT_ORDER_SL + commoId + ":1";
        redisService.getKeys(upperKeys).stream().forEach(key -> {
            BigDecimal entrustPrice = new BigDecimal(key.substring(key.lastIndexOf(":") + 1));
            if (entrustPrice.compareTo(np) >= 0) {
                Map<String, String> map = redisService.map(key);
                if (map != null && map.size() > 0) {
                    map.entrySet().forEach(item -> {
                        try {
                            int orderId = Integer.parseInt(item.getValue());
                            redisService.mapDel(key, item.getKey().toString());
                            doTradeEntrust(orderSimulateMapper.selectByPrimaryKey(orderId));
                        } catch (Exception e) {
                            e.printStackTrace();
                            log.error("限价单行情驱动下单失败，错误消息：{}", e.getMessage());
                        }
                    });
                }
            }
        });
        // 买跌限价策略 委托价 大于等于 最新价  挂单
        String downKey = CacheID.LINIT_ORDER_SL + commoId + ":2";
        redisService.getKeys(downKey).stream().forEach(key -> {
            BigDecimal entrustPrice = new BigDecimal(key.substring(key.lastIndexOf(":") + 1));
            if (entrustPrice.compareTo(np) <= 0) {
                Map<String, String> map = redisService.map(key);
                if (map != null && map.size() > 0) {
                    map.entrySet().forEach(item -> {
                        try {
                            int orderId = Integer.parseInt(item.getValue());
                            redisService.mapDel(key, item.getKey().toString());
                            doTradeEntrust(orderSimulateMapper.selectByPrimaryKey(orderId));
                        } catch (Exception e) {
                            e.printStackTrace();
                            log.error("限价单行情驱动下单失败，错误消息：{}", e.getMessage());
                        }
                    });
                }
            }
        });
    }

    @Override
    public void coverOrderSimulate(OrderSimulate os, int number, int coverType) {
        User user = userMapper.selectByPrimaryKey(os.getUserId());
        //获取期货实时行情
        RealTimeData realTimeData = redisService.get(CacheID.QUOTE_REAL_PREFIX + os.getCommoId(), RealTimeData.class);
        if (null == realTimeData) {
            throw new BussinessException("获取实时行情失败");
        }
        //获取最新价即为平仓价
        BigDecimal np = realTimeData.getNp();
        // 每点合约价值
        BigDecimal pointValue = riskParamService.getParamValue(RiskNID.POINT_VALUE, os.getCommoId(), BigDecimal.class);
        // 最小波动点数
        BigDecimal waveMin = riskParamService.getParamValue(RiskNID.WAVE_POINT_MIN, os.getCommoId(), BigDecimal.class);
        // 汇率
        BigDecimal exchangeRate = riskParamService.getParamValue(RiskNID.EXCHANGE_RATE, os.getCommoId(), BigDecimal.class);

        //计算盈亏金额 ,全部转为人民币 (平仓价 - 开仓价)*汇率*数量*每点合约价值/最小波动点
        BigDecimal profit = (np.subtract(os.getOpenPrice()).multiply(exchangeRate).multiply(new BigDecimal(number)).multiply(pointValue).divide(waveMin, 2, BigDecimal.ROUND_HALF_UP));

        if (OrderSimulate.DIRECTION.T2.code == os.getDirection().intValue()) {
            profit = profit.negate();
        }
        BigDecimal finalProfit = profit;

        // 计算盈亏点数  =  （平仓价 - 开仓价）/最小波动点数*手数  取整数
        int profitPoint = np.subtract(os.getOpenPrice()).multiply(new BigDecimal(number)).divide(waveMin, 2, BigDecimal.ROUND_HALF_UP).intValue();
        if (Order.DIRECTION.T2.code == os.getDirection().intValue()) {
            profitPoint = profitPoint * (-1);
        }
        int finalProfitPoint = profitPoint;

        if (number == os.getNumber()) {// 全部平仓，更新订单状态
            int i = orderSimulateMapper.updateByIdAndVersionSelective(new OrderSimulate(t -> {
                //修改订单信息
                t.setId(os.getId());
                t.setVersion(os.getVersion());
                t.setProfit(finalProfit);
                t.setProfitPoint(finalProfitPoint);
                t.setSettlePrice(np);
                t.setSettleType(coverType);
                t.setSettleTime(DateUtils.getNow());
                t.setStatus(OrderSimulate.STATUS.T6.code);
                t.setExRate(exchangeRate);
            }));
            if (i != 1) throw new BussinessException("平仓更新模拟订单状态失败");
            //处理模拟订单 点买人、投资人账户信息
            coverOrderSimulateDealAccount(os, profit);
        } else if (number > 0 && number < os.getNumber()) {//部分平仓
            // 新增平仓模拟订单
            BigDecimal finalProfit1 = profit;
            // 部分成交，进行拆单处理
            // 原订单号
            String orderNo = os.getOrderNo();

            String orderNo1;
            String orderNo2;
            // 计算新订单相关参数 止盈、止损、履约保证金、止盈保证金（按数量比例分配）
            BigDecimal scale = new BigDecimal(number).divide(new BigDecimal(os.getNumber()), 8, BigDecimal.ROUND_HALF_UP);//保留2位小数，4舍5入
            BigDecimal deposit = scale.multiply(os.getDeposit()).setScale(2, BigDecimal.ROUND_HALF_UP);//履约保证金
            BigDecimal profitDeposit = scale.multiply(os.getProfitDeposit()).setScale(2, BigDecimal.ROUND_HALF_UP);//止盈保证金
            // 处理订单号
            if (orderNo.contains("S")) {
                // 已拆分过
                orderNo1 = orderNo;
                orderNo2 = orderNo.substring(0, orderNo.indexOf("S")) + "S" + getLastPrefix(orderNo.substring(0, orderNo.indexOf("S")), "S");
            } else {
                // 未拆分过
                orderNo1 = orderNo + "S1";
                orderNo2 = orderNo + "S2";
            }
            // 更新原新单
            orderSimulateMapper.updateByIdAndVersionSelective(new OrderSimulate(t -> {
                t.setId(os.getId());
                t.setVersion(os.getVersion());
                t.setOrderNo(orderNo1);
                t.setNumber(os.getNumber() - number);
                t.setDeposit(os.getDeposit().subtract(deposit));
                t.setProfitDeposit(os.getProfitDeposit().subtract(profitDeposit));
            }));


            orderSimulateMapper.insert(new OrderSimulate(t -> {
                BeanUtils.copyProperties(os, t);
                t.setId(null);
                t.setVersion(null);
                t.setOrderNo(orderNo2);
                t.setNumber(number);
                t.setDeposit(deposit);
                t.setProfitDeposit(profitDeposit);
                //修改订单信息
                t.setProfit(finalProfit1);
                t.setProfitPoint(finalProfitPoint);
                t.setStatus(OrderSimulate.STATUS.T6.code);
                t.setSettlePrice(np);
                t.setSettleType(coverType);
                t.setSettleTime(DateUtils.getNow());
                t.setStatus(OrderSimulate.STATUS.T6.code);
                t.setExRate(exchangeRate);
            }));

            OrderSimulate orderSimulate2 = orderSimulateMapper.selectOne(new OrderSimulate(t -> t.setOrderNo(orderNo2)));
            coverOrderSimulateDealAccount(orderSimulate2, profit);
        } else {
            throw new BussinessException("平仓数量有误");
        }

        if (user.getType() == User.TYPE.T3.code) {//操盘手平仓需要平掉对应实盘订单
            //获取模拟盘操盘手下单对应实盘跟单订单
            List<OrderRelation> orderRelations = orderRelationMapper.select(new OrderRelation(orderRelation -> {
                orderRelation.setSimId(os.getId());
                orderRelation.setDelFlag(OrderRelation.DELFLAG.NORMAL.code);
            }));
            orderRelations.forEach(or -> {
                List<Order> orderList = orderMapper.selectLikeOrderNoT3(or.getRealOrderNo() + "%");
                //对应实盘订单全部平仓
                orderList.forEach(order -> {
                    try {
                        Map<String, Object> data = new HashMap<>();
                        data.put("orderId", order.getId());
                        data.put("num", order.getNumber());
                        data.put("settleType", Order.SETTLETYPE.T1.code);
                        redisService.push(CacheID.QUEUE_COVER_ORDER, data);
                    } catch (Exception e) {
                        throw new BussinessException("操盘手平仓，对应跟单实盘平仓失败，订单Id:" + order.getId());
                    }
                });
            });
        }
    }

    @Override
    public void doCoverByLostProfit(BigDecimal np, String commoCode) {
        List<OrderSimulate> list = orderSimulateMapper.selectByLostProfit(np, commoCode);
        List<Integer> ids = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(list)) {
            list.forEach(t -> ids.add(t.getId()));
        }
        log.info("模拟--触发止盈止损平仓,品种：{},最新价：{},处理订单IDS:{}", commoCode, np, JSON.toJSONString(ids));
        if (CollectionUtils.isNotEmpty(list)) {
            for (OrderSimulate order : list) {
                if (order.getDirection().intValue() == OrderSimulate.DIRECTION.T1.code) {
                    if (order.getLoseStopPrice().compareTo(np) != -1) {
                        // 触发止损平仓
                        coverOrderSimulate(order, order.getNumber(), OrderSimulate.SETTLETYPE.T3.code);
                    } else if (order.getProfitStopPrice().compareTo(np) != 1) {
                        // 触发止盈平仓
                        coverOrderSimulate(order, order.getNumber(), OrderSimulate.SETTLETYPE.T2.code);
                    }
                } else {
                    if (order.getLoseStopPrice().compareTo(np) != 1) {
                        // 触发止损平仓
                        coverOrderSimulate(order, order.getNumber(), OrderSimulate.SETTLETYPE.T3.code);
                    } else if (order.getProfitStopPrice().compareTo(np) != -1) {
                        // 触发止盈平仓
                        coverOrderSimulate(order, order.getNumber(), OrderSimulate.SETTLETYPE.T2.code);
                    }
                }
            }
        }

    }

    /**
     * 模拟订单平仓 处理点买人、投资人账户信息
     *
     * @param os
     * @param profit
     */
    public void coverOrderSimulateDealAccount(OrderSimulate os, BigDecimal profit) {
        //点买人的模拟账户
        AccountSimulate userAccountSimulate = accountSimulateMapper.selectByUID(os.getUserId());
        //投资人的模拟账户
//        AccountSimulate investorAccountSimulate = accountSimulateMapper.selectByUID(os.getInvestId());
        if (BigDecimalUtils.isGreaterThan(profit, BigDecimal.ZERO)) { //盈利
            //点买人盈利分成
            BigDecimal profitSharing;
            if (profit.compareTo(os.getProfitDeposit()) == 1) {
                profitSharing = os.getProfitDeposit().multiply(os.getAllotRate());
            } else {
                profitSharing = profit.multiply(os.getAllotRate());
            }

            //增加两条资金记录,一条是返回履约保证金，一条是盈利金额
            int m1 = accountLogSimulateMapper.insert(AccountLogSimulate.buildLog(userAccountSimulate.getUserId(), os.getDeposit(), AccountLogSimulate.TYPE.T19, userAccountSimulate, "解冻履约保证金" + os.getDeposit() + "元。模拟订单ID：" + os.getId()));
            if (m1 != 1) throw new BussinessException("添加解冻履约保证金资金记录失败");
            int m2 = accountLogSimulateMapper.insert(AccountLogSimulate.buildLog(userAccountSimulate.getUserId(), profitSharing, AccountLogSimulate.TYPE.T25, userAccountSimulate, "策略盈利" + profitSharing + "元。模拟订单ID：" + os.getId()));
            if (m2 != 1) throw new BussinessException("添加策略盈利资金记录失败");
            int a1 = accountSimulateMapper.updateAccountSimulate(profitSharing, os.getDeposit().add(profitSharing), os.getDeposit().negate(), userAccountSimulate.getUserId());
            if (a1 != 1) throw new BussinessException("更新点买人模拟账户失败");

            // 投资人账户修改
//            BigDecimal amount = os.getProfitDeposit().subtract(profitSharing);
//            int b1 = accountSimulateMapper.updateAccountSimulate(profitSharing.negate(), amount, os.getProfitDeposit().negate(), investorAccountSimulate.getUserId());
//            //增加两条资金记录,一条是返回履约保证金，一条是盈利金额
//            if (b1 != 1) throw new BussinessException("更新投资人模拟账户失败");
//            int n1 = accountLogSimulateMapper.insert(AccountLogSimulate.buildLog(investorAccountSimulate.getUserId(), os.getProfitDeposit(), AccountLogSimulate.TYPE.T22, investorAccountSimulate, "解冻投资人止盈保证金" + os.getProfitDeposit() + "元。模拟订单ID：" + os.getId()));
//            if (n1 != 1) throw new BussinessException("添加解冻投资人止盈保证金资金记录失败");
//            int n2 = accountLogSimulateMapper.insert(AccountLogSimulate.buildLog(investorAccountSimulate.getUserId(), profitSharing, AccountLogSimulate.TYPE.T26, investorAccountSimulate, "策略盈利，扣除点买人盈利分成" + profitSharing + "元。模拟订单ID：" + os.getId()));
//            if (n2 != 1) throw new BussinessException("添加投资人策略盈利资金记录失败");

        } else {//亏损
            BigDecimal lostAmount;
            if (profit.negate().compareTo(os.getDeposit()) == 1) {
                lostAmount = os.getDeposit();
            } else {
                lostAmount = profit.negate();
            }
            //增加两条资金记录,一条是返回履约保证金，一条是亏损金额
            int m1 = accountLogSimulateMapper.insert(AccountLogSimulate.buildLog(userAccountSimulate.getUserId(), os.getDeposit(), AccountLogSimulate.TYPE.T19, userAccountSimulate, "解冻履约保证金" + os.getDeposit() + "元。模拟订单ID：" + os.getId()));
            if (m1 != 1) throw new BussinessException("添加解冻履约保证金资金记录失败");
            int m2 = accountLogSimulateMapper.insert(AccountLogSimulate.buildLog(userAccountSimulate.getUserId(), lostAmount, AccountLogSimulate.TYPE.T26, userAccountSimulate, "策略亏损" + lostAmount + "元。模拟订单ID：" + os.getId()));
            if (m2 != 1) throw new BussinessException("添加策略亏损资金记录失败");
            int a1 = accountSimulateMapper.updateAccountSimulate(lostAmount.negate(), os.getDeposit().subtract(lostAmount), os.getDeposit().negate(), userAccountSimulate.getUserId());
            if (a1 != 1) throw new BussinessException("更新点买人模拟账户失败");

//            int b1 = accountSimulateMapper.updateAccountSimulate(lostAmount, os.getProfitDeposit().add(lostAmount), os.getProfitDeposit().negate(), investorAccountSimulate.getUserId());
//            if (b1 != 1) throw new BussinessException("更新投资人模拟账户失败");
//            int n1 = accountLogSimulateMapper.insert(AccountLogSimulate.buildLog(investorAccountSimulate.getUserId(), os.getProfitDeposit(), AccountLogSimulate.TYPE.T22, investorAccountSimulate, "解冻投资人止盈保证金" + os.getProfitDeposit() + "元。模拟订单ID：" + os.getId()));
//            if (n1 != 1) throw new BussinessException("添加解冻投资人止盈保证金资金记录失败");
//            int n2 = accountLogSimulateMapper.insert(AccountLogSimulate.buildLog(investorAccountSimulate.getUserId(), lostAmount, AccountLogSimulate.TYPE.T26, investorAccountSimulate, "策略亏损，扣除点买人亏损" + lostAmount + "元。模拟订单ID：" + os.getId()));
//            if (n2 != 1) throw new BussinessException("添加投资人策略亏损资金记录失败");
        }
    }


    /**
     * 查询同一订单最大尾号
     *
     * @param orderNo 原始订单号
     * @param regex   分割标志
     * @return
     */
    private synchronized int getLastPrefix(String orderNo, String regex) {
        List<String> list = orderSimulateMapper.selectOrderNos(orderNo);
        List<Integer> prefixs = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(list)) {
            for (String s : list) {
                if (s.contains(regex)) {
                    prefixs.add(Integer.parseInt(s.split(regex)[1]));
                }
            }
        }
        Collections.sort(prefixs);
        return prefixs.get(prefixs.size() - 1) + 1;
    }

    /**
     * 模拟下单校验
     *
     * @param accountSimulate
     * @param commoId
     * @param num
     * @param lostPoint
     * @param point
     * @param openWay
     * @param entrustPrice
     * @param direction
     * @param resource        @return
     */
    private String checkTrade(AccountSimulate accountSimulate, Integer commoId, Integer num, Integer lostPoint, Integer point, Integer openWay, BigDecimal entrustPrice, Integer direction, String resource) {

        if (StrUtil.isBlank(resource)) return "订单来源不正确";

        if (accountSimulate == null) return "查询用户模拟账户信息失败";

        if (commoId == null || commoId <= 0) return "交易品种不正确";

        if (num == null || num <= 0) return "交易数量不正确";

        if (!OrderSimulate.OPENWAY.getEnumMap().containsKey(openWay)) return "开仓类型不正确";

        if (!OrderSimulate.DIRECTION.getEnumMap().containsKey(direction)) return "交易方向不正确";

        // 止损点数列表
        List<Integer> lostStopPoints = riskParamService.getParam2IntList(RiskNID.LOSE_STOP_POINT, commoId);
        if (!lostStopPoints.contains(lostPoint)) return "止损金额选择错误";

        if (openWay == OrderSimulate.OPENWAY.T2.code && (entrustPrice == null || entrustPrice.compareTo(BigDecimal.ZERO) != 1))
            return "委托价格不正确";

        return null;

    }
}
